﻿using System;
using System.Drawing;
using System.Text;
using System.Drawing.Imaging;

namespace CalcSharp.Core
{
    /// <summary>
    /// A color converter class
    /// </summary>
    public class Int24
    {
        private byte r;
        private byte g;
        private byte b;

        private static void TestArrayLength(byte[] array, int l)
        {
            if (array.Length != l) throw new CSException("Not enough data");
        }

        public static int RGB2Int(byte R, byte G, byte B)
        {
            int temp = R << 16;
            int temp2 = G << 8;
            temp += temp2;
            temp2 = B;
            temp += temp2;
            return temp;
        }

        #region Properties

        public byte R
        {
            get { return this.r; }
            set { this.r = value; }
        }

        public byte G
        {
            get { return this.g; }
            set { this.g = value; }
        }

        public byte B
        {
            get { return this.b; }
            set { this.b = value; }
        }

        public static int MaxValue
        {
            get { return 0xFFFFFF; }
        }

        public static int MinValue
        {
            get { return 0; }
        }

        #endregion

        #region constructors
        public Int24()
        {
            this.r = 0;
            this.g = 0;
            this.b = 0;
        }

        public Int24(int num)
        {
            this.FromInt32(num);
        }

        public Int24(int R, int G, int B)
        {
            this.FromRGB(R, G, B);
        }

        public void FromRGB(int R, int G, int B)
        {
            if (R < 0) this.r = 0;
            else if (R > 255) this.r = 255;
            else this.r = (byte)R;

            if (G < 0) this.g = 0;
            else if (G > 255) this.g = 255;
            else this.g = (byte)R;

            if (B < 0) this.b = 0;
            else if (B > 255) this.b = 255;
            else this.b = (byte)R;
        }

        public Int24(Color c)
        {
            this.FromColor(c);
        }
        #endregion

        #region Operators & Overrides

        public static implicit operator Int24(int input)
        {
            return new Int24(input);
        }

        public static implicit operator Int96(Int24 input)
        {
            return input.ToInt96();
        }

        public static implicit operator int(Int24 input)
        {
            return input.ToInt32();
        }

        public static Int24 operator +(Int24 i1, Int24 i2)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i1.R + i2.R;
            g = i1.G + i2.G;
            b = i1.B + i2.B;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator +(Int24 i, int n)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i.R + n;
            g = i.G + n;
            b = i.B + n;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator -(Int24 i1, Int24 i2)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i1.R - i2.R;
            g = i1.G - i2.G;
            b = i1.B - i2.B;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator -(Int24 i, int n)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i.R - n;
            g = i.G - n;
            b = i.B - n;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator *(Int24 i1, Int24 i2)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i1.R * i2.R;
            g = i1.G * i2.G;
            b = i1.B * i2.B;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator *(Int24 i, int n)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i.R * n;
            g = i.G * n;
            b = i.B * n;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator *(Int24 i, short n)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i.R * n;
            g = i.G * n;
            b = i.B * n;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator /(Int24 i1, Int24 i2)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i1.R / i2.R;
            g = i1.G / i2.G;
            b = i1.B / i2.B;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static Int24 operator /(Int24 i, int n)
        {
            Int24 ret = new Int24();
            int r, g, b;
            r = i.R / n;
            g = i.G / n;
            b = i.B / n;
            if (r > 255) r = 255;
            if (r < 0) r = 0;
            if (g > 255) g = 255;
            if (g < 0) g = 0;
            if (b > 255) b = 255;
            if (b < 0) b = 0;
            ret.R = (byte)r;
            ret.G = (byte)g;
            ret.B = (byte)b;
            return ret;
        }

        public static bool operator ==(Int24 a, Int24 b)
        {
            return (a.R == b.R && a.G == b.G && a.B == b.B);
        }

        public static bool operator !=(Int24 a, Int24 b)
        {
            return (a.R != b.R || a.G != b.G || a.B != b.B);
        }

        public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType()) return false;
            return (this == (Int24)obj);
        }

        public override int GetHashCode()
        {
            return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode();
        }
        #endregion

        #region From
        public void FromInt32(int num)
        {
            int w = num;
            int temp;
            if (w < 0) w = Math.Abs(w);
            if (w > 0xFFFFFF) w = 0xFFFFFF;

            temp = w & 0xFF0000;
            temp = temp >> 16;
            this.r = Convert.ToByte(temp);

            temp = w & 0x00FF00;
            temp = temp >> 8;
            this.g = Convert.ToByte(temp);

            temp = w & 0x0000FF;
            this.b = Convert.ToByte(temp);
        }

        public void FromInt96(Int96 input)
        {
            this.r = input.NormR;
            this.g = input.NormG;
            this.b = input.NormB;
        }

        public void FromColor(Color c)
        {
            this.r = c.R;
            this.b = c.B;
            this.g = c.G;
        }

        public void FromByteArray(byte[] array, PixelFormat f)
        {
            ushort data;
            switch (f)
            {
                case PixelFormat.Format16bppArgb1555:
                case PixelFormat.Format16bppRgb555:
                    TestArrayLength(array, 2);
                    data = BitConverter.ToUInt16(array, 0);
                    this.r = (byte)((data & 0x7C00) >> 10);
                    this.g = (byte)((data & 0x3E0) >> 5);
                    this.b = (byte)((data & 0x1F));
                    break;
                case PixelFormat.Format16bppRgb565:
                    TestArrayLength(array, 2);
                    data = BitConverter.ToUInt16(array, 0);
                    this.r = (byte)((data & 0xF800) >> 11);
                    this.g = (byte)((data & 0x7E0) >> 6);
                    this.b = (byte)(data & 0x1F);
                    break;
                case PixelFormat.Format24bppRgb:
                    TestArrayLength(array, 3);
                    this.r = array[0];
                    this.g = array[1];
                    this.b = array[2];
                    break;
                case PixelFormat.Format32bppArgb:
                case PixelFormat.Format32bppRgb:
                    TestArrayLength(array, 4);
                    this.r = array[1];
                    this.g = array[2];
                    this.b = array[3];
                    break;
                case PixelFormat.Format32bppPArgb:
                    TestArrayLength(array, 4);
                    byte alpha = array[0];
                    this.r = (byte)(array[1] / alpha);
                    this.g = (byte)(array[2] / alpha);
                    this.b = (byte)(array[3] / alpha);
                    break;
                default:
                    throw new CSException("Unsuported pixel format");
            }
        }

        public void FromByteArray(byte[] array)
        {
            byte backup;
            Array.Reverse(array);
            ushort b2, b;
            switch (array.Length)
            {
                case 1:
                    backup = (byte)(array[0] & 192);
                    this.r = (byte)(backup >> 5);
                    backup = (byte)(array[0] & 28);
                    this.g = (byte)(backup >> 2);
                    this.b = (byte)(array[0] & 3);
                    break;
                case 2:
                    //5650 format
                    b = BitConverter.ToUInt16(array, 0);
                    b2 = (ushort)(b & 0xf800);
                    this.r = (byte)(b2 >> 11);
                    b2 = (ushort)(b & 0x07e0);
                    this.g = (byte)(b2 >> 5);
                    this.b = (byte)(b & 0x001f);
                    break;
                case 3:
                    this.r = array[0];
                    this.g = array[1];
                    this.b = array[2];
                    break;
                case 4:
                    this.r = array[1];
                    this.g = array[2];
                    this.b = array[3];
                    break;
            }
        }

        public void FromString(string val)
        {
            string[] raw = val.Split(';');
            if (raw.Length != 3) throw new CSException("Can't parse as HSL value: " + val);
            raw[0] = raw[0].Replace("R: ", "");
            raw[1] = raw[1].Replace(" G: ", "");
            raw[2] = raw[2].Replace(" B: ", "");
            this.R = Convert.ToByte(raw[0]);
            this.G = Convert.ToByte(raw[1]);
            this.B = Convert.ToByte(raw[2]);
        }
        #endregion

        #region To
        public int ToInt32()
        {
            int temp = this.r << 16;
            int temp2 = this.g << 8;
            temp += temp2;
            temp2 = this.b;
            temp += temp2;
            return temp;
        }

        public Int96 ToInt96()
        {
            return new Int96(this.r, this.g, this.b);
        }

        public byte[] ToByteArray()
        {
            byte[] ret = new byte[3];
            ret[0] = this.r;
            ret[1] = this.g;
            ret[2] = this.b;
            return ret;
        }

        public Color ToColor()
        {
            return Color.FromArgb(this.r, this.g, this.b);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("R: " + r.ToString() + "; ");
            sb.Append("G: " + g.ToString() + "; ");
            sb.Append("B: " + b.ToString());
            return sb.ToString();
        }
        #endregion
    }
}
